#include "WidgetTree.h"
#include "Math/Math.h"
#include "Widgets/Mouse/MouseCursorManager.h"
#include "Widgets/Keyboard/KeyboardWidget.h"
#include "Widgets/Window.h"
#include <cmath>
#include <iostream>

TWeakObjectPtr<RMouseDrag> CWidgetTree::Dragging=nullptr;

TWeakObjectPtr<RKeyboardWidget> CWidgetTree::KeyboardFocused=nullptr;

CWidgetTree::CWidgetTree(RWindow* OwnerWindow)
:Owner(OwnerWindow)
{
    DragDetector=std::make_unique<CDragDetector>();
}


void CWidgetTree::UpdateWindowGrids(const SIntPoint& InWindowSize)
{
    WindowGrids.Clear();
    DebugMouseWidgetID=0;

    //split window
    int ColumnNum=std::ceil(InWindowSize.X /(float)GridSize.X);
    int RowNum=std::ceil(InWindowSize.Y /(float)GridSize.Y);
    GridNum=SIntPoint(ColumnNum,RowNum);
    WindowGrids.Resize(RowNum);
    for(int RowIndex=0;RowIndex<RowNum;RowIndex++)
    {

        for(int ColumnIndex=0;ColumnIndex<ColumnNum;ColumnIndex++)
        {
            WindowGrids[RowIndex].Add(std::make_shared<SWindowGrid>());
            WindowGrids[RowIndex][ColumnIndex]->Row=RowIndex;
            WindowGrids[RowIndex][ColumnIndex]->Column=ColumnIndex;
        }        
    }
}


void CWidgetTree::HoldGrids(RMouseWidget* InWidget)
{
    SIntPoint LeftTop=
        SIntPoint((int)(InWidget->PresentAbsPosition.X /GridSize.X),
            (int)(InWidget->PresentAbsPosition.Y /GridSize.Y));


    SIntPoint RightBottom= LeftTop+
        SIntPoint((int)floor(((InWidget->PresentAbsPosition.X +InWidget->PresentSize.X) /GridSize.X)),
            (int)floor(((InWidget->PresentAbsPosition.Y +InWidget->PresentSize.Y) /GridSize.Y)));


    for(int RowIndex=CMathBase::Max(0,LeftTop.Y) ;RowIndex<=CMathBase::Min(GridNum.Y-1,RightBottom.Y);RowIndex++)
    {
        for(int ColumnIndex=CMathBase::Max(0,LeftTop.X);ColumnIndex<=CMathBase::Min(GridNum.X-1,RightBottom.X);ColumnIndex++)
        {
            WindowGrids[RowIndex][ColumnIndex]->Widgets.Add(InWidget);  //  .push_back(InWidget);
        }        
    }
}

void CWidgetTree::UnHoldGrids(RMouseWidget* InWidget)
{
    SIntPoint LeftTop=
        SIntPoint((int)(InWidget->PresentAbsPosition.X /GridSize.X),
            (int)(InWidget->PresentAbsPosition.Y /GridSize.Y));

    SIntPoint RightBottom= LeftTop+
        SIntPoint((int)floor(((InWidget->PresentAbsPosition.X +InWidget->PresentSize.X) /GridSize.X)),
            (int)floor(((InWidget->PresentAbsPosition.Y +InWidget->PresentSize.Y) /GridSize.Y)));

    for(int RowIndex=LeftTop.Y;RowIndex<=CMathBase::Min(RightBottom.Y,GridNum.Y-1);RowIndex++)
    { 
        for(int ColumnIndex=LeftTop.X;ColumnIndex<=CMathBase::Min(RightBottom.X,GridNum.X-1);ColumnIndex++)
        {
             WindowGrids[RowIndex][ColumnIndex]->Widgets.RemoveFirst(InWidget);
        }        
    }    

}

void CWidgetTree::UpdateHorveringWidget(const SIntPoint & InMousePosition)
{

    //address enter and exit event
     //exit first
    TVector<RMouseWidgetPtr> Removed;
    for(auto HoveringWidget:HoveringWidgets)
    {
        if(!HoveringWidget->IsMouseIn(InMousePosition))
        {
            HoveringWidget->NotifyExited();
            Removed.Add(HoveringWidget);
        }
    }

    for(auto RemovedWidget:Removed)
    {
        HoveringWidgets.Remove(RemovedWidget);
    }


    auto Grid=InMousePosition / GridSize;
    if(Grid.X>=GridNum.X || Grid.Y>=GridNum.Y || Grid.X <0 || Grid.Y <0 )
    {
        return;
    }

    //try enter widget
    auto Widgets=WindowGrids[Grid.Y][Grid.X]->Widgets;
    for(int32_t WidgetIndex=(int32_t)Widgets.Num()-1;WidgetIndex>=0;WidgetIndex--)
    {
        if(!Widgets[WidgetIndex]->IsMouseIn(InMousePosition))
        {
            continue;
        }

        if(HoveringWidgets.Contains(Widgets[WidgetIndex]))
        {
            continue;
        }

        HoveringWidgets.Add(Widgets[WidgetIndex]);
        Widgets[WidgetIndex]->NotifyEntered();
    }

}

void CWidgetTree::StartDetect(TSharedObjectPtr<RMouseDrag>  InDrag)
{
    assert(InDrag);
    DragDetector->StartDetect(InDrag->GetOwnerMouse(),EMouseButton::Left);
}

void CWidgetTree::PostHoldGrids()
{
    // some mouse widget might first time hold grid and just under the mouse 
    UpdateHorveringWidget(GetWindow()->GetMousePositionInContent());
}


bool CWidgetTree::ProcessMouseEvent(const SMouseButtonEvent& MouseEvent)
{
    bool Customed;
    auto WidgetDetermineCursor=ApplyMouseEvent(MouseEvent,Customed);
    
    if(WidgetDetermineCursor)
    {
        auto NewShape=WidgetDetermineCursor->CursorShape.Get();
        RMouseCursorManager::Get()->UseCursor(
            NewShape);
    }
    else
    {
        RMouseCursorManager::Get()->UseDefaultCursor();
    }
    return Customed;

}

TWeakObjectPtr<RMouseWidget> CWidgetTree::ApplyMouseEvent(const SMouseButtonEvent &MouseEvent,bool& Customed)
{
    auto MousePos=GetWindow()->GetMousePositionInContent();
    CurrentMousePosition=SVec2(MousePos.X,MousePos.Y);
    
    //check if unfocus keyboard widget
    if(MouseEvent.GetType()==EMouseEventType::ButtonDown)
    {
        if(KeyboardFocused && !KeyboardFocused->IsMouseIn(MousePos))
        {
            UnfocusWidget();
        }
    }


    //address enter and exit event
    if(MouseEvent.GetType()==EMouseEventType::Move)
    {
        UpdateHorveringWidget(MousePos);
    }

    if(DragDetector->IsDetecting())
    {
        auto DragWidget=DragDetector->GetTarget();
        if(DragDetector->Detect(MouseEvent))
        {
            StartDrag(DragWidget->Drag);
            
            Customed=true;

            return  DragWidget;
        }

    }
    //in case mouse is out of dragging widget
    else if(Dragging)
    {   
        if(MouseEvent.GetType()==EMouseEventType::ButtonUp)
        {
            Customed=true;
            StopDrag();

            //WH::Cout()<<"Stop Drag"<<WH::EndL();
            return nullptr;
        }
        
        else if( Dragging->ProcessMouseEvent(this,MouseEvent))
        {
            Customed=true;

            return nullptr;
            //return Dragging->GetOwnerMouse();
        }
    }

    //TODO consider the situation that the mouse on the right/bottom edge;
    auto Grid=MousePos / GridSize;

    if(Grid.X>=GridNum.X || Grid.Y>=GridNum.Y  || Grid.X<0 || Grid.Y<0)
    {
        //out of window
        Customed=false;
        return nullptr;
    }

    auto Widgets=WindowGrids[Grid.Y][Grid.X]->Widgets;

    RMouseWidget * TopMouseInWidget=nullptr;
    for(int32_t WidgetIndex=(int32_t)Widgets.Num()-1;WidgetIndex>=0;WidgetIndex--)
    {
        if(!Widgets[WidgetIndex]->IsMouseIn(MousePos))
        {
            continue;
        }
        if(!TopMouseInWidget)
        {
            TopMouseInWidget=Widgets[WidgetIndex];
        }

        if(Widgets[WidgetIndex]->ProcessMouseEvent(MouseEvent))
        {
            Customed=true;
            return TopMouseInWidget;//Widgets[WidgetIndex];
        }
    }
    
    Customed=false;
    return TopMouseInWidget;
}

bool CWidgetTree::ProcessTextInput(const STextInputEvent &BoardkeyEvent)
{
    if(KeyboardFocused)
    {   
        return KeyboardFocused->ProcessTextInput(BoardkeyEvent);
    }
    return false;
}

bool CWidgetTree::ProcessKeyboardEvent(const SKeyboardEvent& BoardkeyEvent)
{
    if(KeyboardFocused)
    {
        return KeyboardFocused->ProcessKeyboardEvent(BoardkeyEvent);
    }
    return false;   

}

RWindow* CWidgetTree::GetWindow() const
{
    return Owner;
}


void CWidgetTree::FocusWidget(TSharedObjectPtr<RKeyboardWidget> InWidget)
{
    if(KeyboardFocused==InWidget)
    {
        return;
    }

    if(KeyboardFocused)
    {
        KeyboardFocused->OnUnfocused();
    }

    // if(InWidget&&InWidget->IsA<RWindow>()) //window have a ProcessTextInput(), will cause infinite invoke with widget tree's ProcessTextInput();
    // {
    //     KeyboardFocused=nullptr;
    // }
    // else
    {
        KeyboardFocused=InWidget;
    }

    if(KeyboardFocused)
    {
        KeyboardFocused->OnFocused();
    }        
}

void CWidgetTree::UnfocusWidget()
{
    if(KeyboardFocused)
    {
        KeyboardFocused->OnUnfocused();
        KeyboardFocused=nullptr;
    }
}

bool CWidgetTree::IsFocused(TSharedObjectPtr<RKeyboardWidget> InWidget)
{
    return KeyboardFocused==InWidget;
}
